﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Configuration;
using System.Windows;
using System.Security.AccessControl;
using System.Security.Permissions;
using System.Security;
using System.Reflection;

public static class LogHelper
{
    #region private variable
    //{datetime} {MethodInfo|FileInfo} {CustomInfo} 
    const string formatWithMethodInfo = "{0} | {1} | {2}";
    const string formatErrorWithMethodInfo = "Err: {0} | {1} | {2}";
    const string formatWithTime = "{0} | {1} ";
    const string formatTimeWithMilliSecond = "HH:mm:ss fff";

    #endregion

    #region private method
    /// <summary>
    /// get current call method name
    /// </summary>
    /// <returns>FullName.Name,example as [WpfTraceSolution.MainWindow.button1_Click]</returns>
    static string GetExecutingMethodName()
    {
        string result = "Unknown";
        StackTrace trace = new StackTrace(false);

        for (int index = 0; index < trace.FrameCount; ++index)
        {
            StackFrame frame = trace.GetFrame(index);
            MethodBase method = frame.GetMethod();
            Type declaringType = method.DeclaringType;
            if (declaringType != typeof(LogHelper))
            {
                result = string.Concat(method.DeclaringType.FullName, ".", method.Name);
                break;
            }
        }

        return result;
    }

    /// <summary>
    /// get Execute details ,output example as [F:\WpfTraceSolution\MainWindow.xaml.cs(79) | WpfTraceSolution.MainWindow.btnWriteWithFileInfo_Click]
    /// </summary>
    /// <returns></returns>
    static string GetExecutingInfo()
    {
        string result = "Unknown";
        StringBuilder sbInfo = new StringBuilder();
        StackTrace trace = new StackTrace(true);

        for (int index = 0; index < trace.FrameCount; ++index)
        {
            StackFrame frame = trace.GetFrame(index);
            MethodBase method = frame.GetMethod();
            Type declaringType = method.DeclaringType;
            if (declaringType != typeof(LogHelper))
            {
                sbInfo.AppendFormat("{0}", frame.GetFileName());
                sbInfo.AppendFormat("({0}) ", frame.GetFileLineNumber());
                sbInfo.AppendFormat("| {0}.{1}", method.DeclaringType.FullName, method.Name);
                result = sbInfo.ToString();
                break;
            }
        }

        return result;
    }
    #endregion

    #region public methods
    /// <summary>
    /// init by configuration
    /// </summary>
    public static void Init()
    {
        #region define
        string logPathKey = "logPath", logDestinationKey = "logDestination";
        string logPath = string.Empty, logDestination = string.Empty;
        FileStream stream;
        FileMode fileMode;
        #endregion

        #region get destination
        //no destination,return
        if (!ConfigurationManager.AppSettings.AllKeys.Contains(logDestinationKey)) return;
        logDestination = ConfigurationManager.AppSettings[logDestinationKey].Trim();

        int logType = 0;
        Int32.TryParse(logDestination, out logType);
        #endregion

        #region switch log type
        Trace.AutoFlush = true;
        switch (logType)
        {
            case 0:
                {
                    //0 - No output
                    Trace.Listeners.Clear();
                    return;
                }
            case 1:
                {
                    //1 - Debugger (Such as DbgView)
                    //use default listener
                    return;
                }
            case 2:
                {
                    //2 - File (Overwrite the old file and doesn't close it until application exits)
                    Trace.Listeners.Clear();
                    fileMode = FileMode.Create; break;
                }
            case 3:
                {
                    //3 - File (Append the log at the end of file and close it after each log output)
                    Trace.Listeners.Clear();
                    fileMode = FileMode.Append; break;
                }
            case 4:
                {
                    //4 - Debugger&File (Append the log at the end of file and close it after each log output)
                    fileMode = FileMode.Append; break;
                }

            default: return;
        }
        #endregion

        #region check path
        //path is null
        logPath = ConfigurationManager.AppSettings[logPathKey].Trim();
        if (string.IsNullOrEmpty(logPath)) return;

        //path has invalid char
        var pathCharArray = logPath.ToCharArray();
        if (pathCharArray.Any(o => Path.GetInvalidPathChars().Contains(o)))
            return;

        //FileName has invalid char
        //note : invalid file name chars count is 41, 
        //invalid path  chars count  is 36
        //and , the top 36 of invalid file name chars  are  same as invalid path chars
        //so,first check path invalid chars,second check filename,only filename
        var filenameCharArray = Path.GetFileName(logPath).ToCharArray();
        if (filenameCharArray.Any(o => Path.GetInvalidFileNameChars().Contains(o)))
            return;

        //EnvironmentVariables Path
        if (logPath.Contains('%'))
            logPath = Environment.ExpandEnvironmentVariables(logPath);

        //cheng relative path to absolute path.
        if (String.IsNullOrEmpty(Path.GetPathRoot(logPath)))
            logPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, logPath);
        #endregion

        #region file log
        //risk:directory readonly;need administrator right to createfile;and so on
        //use try-catch
        try
        {
            if (!Directory.Exists(Path.GetDirectoryName(logPath)))
                Directory.CreateDirectory(Path.GetDirectoryName(logPath));

            stream = File.Open(logPath, fileMode, FileAccess.Write, FileShare.ReadWrite);
            TextWriterTraceListener text = new TextWriterTraceListener(stream);
            //text.TraceOutputOptions = TraceOptions.DateTime;
            Trace.Listeners.Add(text);
        }
        catch (Exception ex)
        {
            Trace.Write(ex);
        }
        #endregion

    }

    /// <summary>
    /// write by format string
    /// </summary>
    /// <param name="format">A format string that contains zero or more format items, which correspond   to objects in the args array.</param>
    /// <param name="args"> An object array containing zero or more objects to format.</param>
    /// <exception >
    /// string.format fail,only output format 
    /// </exception>
    public static void Write(string format, params object[] args)
    {
        try
        {
            Trace.WriteLine(string.Format(format, args));
        }
        catch (Exception ex)
        {
            Trace.Write(ex);
        }
        finally
        {
            Trace.WriteLine(format);
        }
    }

    /// <summary>
    /// example:{2011/8/10 10:10:49 | WpfTraceSolution.MainWindow.btnWriteWithMethod_Click | Do Write With method]
    /// </summary>
    /// <param name="info"></param>
    public static void WriteWithMethod(string info)
    {
        Trace.WriteLine(string.Format(formatWithMethodInfo, DateTime.Now.ToString(), GetExecutingMethodName(), info));
    }

    /// <summary>
    /// example: [2011/8/10 10:11:59 | F:\WpfTraceSolution\MainWindow.xaml.cs(79) | WpfTraceSolution.MainWindow.btnWriteWithFileInfo_Click | Do Write With fileInfo]
    /// must has pdb file,otherse ,no file info
    /// </summary>
    /// <param name="info">custom info</param>
    public static void WriteWithFileInfo(string info)
    {
        Trace.WriteLine(string.Format(formatWithMethodInfo, DateTime.Now.ToString(), GetExecutingInfo(), info));
    }

    /// <summary>
    /// output info with time,the time has milliseccond,but no date.
    /// example as :[11:37:18 560 | Do Write With Time ]
    /// </summary>
    /// <param name="info"></param>
    public static void WriteWithTime(string info)
    {
        Trace.WriteLine(string.Format(formatWithTime, DateTime.Now.ToString(formatTimeWithMilliSecond), info));
    }

    /// <summary>
    /// output info with time,the time has milliseccond,but no date.
    /// receive format params
    /// example as :[11:37:18 560 | Do Write With Time ]
    /// </summary>
    /// <param name="format"></param>
    /// <param name="args"></param>
    /// <returns></returns>
    /// <exception >
    /// string.format fail,only output format 
    /// </exception>
    public static void WriteWithTime(string format, params object[] args)
    {
        try
        {
            WriteWithTime(string.Format(format, args));
        }
        catch (Exception ex)
        {
            Trace.Write(ex);
        }
        finally
        {
            WriteWithTime(format);
        }
    }

    /// <summary>
    /// output info with datetime 
    /// example as :[2011/8/10 11:54:31 | Do Write With DateTime  ]
    /// </summary>
    /// <param name="info"></param>
    public static void WriteWithDateTime(string info)
    {
        Trace.WriteLine(string.Format(formatWithTime, DateTime.Now.ToString(), info));
    }

    /// <summary>
    /// output info with datetime 
    /// receive format params
    /// example as :[2011/8/10 11:54:31 | Do Write With DateTime  ]
    /// </summary>
    /// <param name="format">A format string that contains zero or more format items, which correspond   to objects in the args array.</param>
    /// <param name="args"> An object array containing zero or more objects to format.</param>
    /// <returns></returns>
    /// <exception >
    /// string.format fail,only output format 
    /// </exception>
    public static void WriteWithDateTime(string format, params object[] args)
    {
        try
        {
            WriteWithTime(string.Format(format, args));
        }
        catch (Exception ex)
        {
            Trace.Write(ex);
        }
        finally
        {
            WriteWithTime(format);
        }
    }

    /// <summary>
    /// output datetime and exception.message
    /// </summary>
    /// <param name="ex">current exception.</param>
    /// <param name="caption">custom Caption</param>
    public static void WriteException(Exception ex, string caption)
    {
        StringBuilder sb = new StringBuilder();
        sb.AppendFormat("{0}    {1}", DateTime.Now.ToString(), caption);
        sb.AppendLine();
        sb.Append(ex.ToString());
        Trace.Write(sb.ToString());
    }

    /// <summary>
    /// example:{2011/8/10 10:10:49 | WpfTraceSolution.MainWindow.btnWriteWithMethod_Click | Do Write With method]
    /// </summary>
    /// <param name="info"></param>
    public static void WriteExceptionWithMethod(string info)
    {
        Trace.WriteLine(string.Format(formatErrorWithMethodInfo, DateTime.Now.ToString(), GetExecutingMethodName(), info));
    }
    #endregion
}

